iT邦幫忙

2022 iThome 鐵人賽

DAY 15
0
Mobile Development

Flutter 30: from start to store系列 第 15

Flutter頁面的建構:Icon, TextInput, ListView

  • 分享至 

  • xImage
  •  

今天和大家一起來看一些UI組件:

  • Icon
  • TextInput
  • ListView

好的,那我們就開始吧!


Icon

  • 要直接取用Flutter 內預設提供的material design元件,需要先在pubspec.yaml加入
    uses-material-design:true

    flutter:
      uses-material-design: true
    
  • 接著,建立Icon 實體,並在IconData位置填入想使用的Icon名稱(從Icons中選取)

    Icon(
      Icons.favorite, // iconData
      color: Colors.pink,
      size: 24.0,
      semanticLabel: 'Text to announce in accessibility modes',
    ),
    

TextInput

  • textInput可以讓使用者輸入訊息

    TextField(
      decoration: InputDecoration(
        border: OutlineInputBorder(),
        hintText: 'Enter a search term',
      ),
    ),
    
  • 在手機上點選TextInput會觸發鍵盤,可以使用KeyboardType來變更鍵盤型態,例如

    keyboardType: TextInputType.number
    

    可以將鍵盤變更為數字鍵盤。 (若在ios simulator上沒有出現鍵盤請用command+K啟用模擬器鍵盤)

  • 透過 maxLineminLine可以設定最大和最小行數

  • 我們也可以建立一個controller來監聽TextInput輸入文字的變化(輸入或刪除),直接用controller來改變輸入框內容controller.text = 'DESIRED_VALUE'

  • 設置onChanged可以指定文字產生變化時要執行的動作,但是注意直接使用controller改動文字並不會觸發onChanged

  • onSubmit則是可以在使用者按下手機鍵盤上送出/電腦Enter鍵時,觸發送出後的行為

    class MyStatefulWidget extends StatefulWidget {
      const MyStatefulWidget({super.key});
    
      @override
      State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
    }
    
    class _MyStatefulWidgetState extends State<MyStatefulWidget> {
      final TextEditingController _controller = TextEditingController(); // 建立controller
    
      @override
      void dispose() {
        _controller.dispose(); // 在離開頁面、Widget被銷毀時,要記得將controller註銷
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              const Text('What number comes next in the sequence?'),
              const Text('1, 1, 2, 3, 5, 8...?'),
              TextField(
                controller: _controller, // 指派Controller給TextField
                onChanged: (String value) async { // 隨時監聽輸入文字的變化
                  if (value != '13') {
                    return;
                  }
                  await showDialog<void>(  // 若當下的輸入值是13,會跳出視窗提示
                    context: context, 
                    builder: (BuildContext context) {
                      return AlertDialog(
                        title: const Text('That is correct!'),
                        content: const Text('13 is the right answer.'),
                        actions: <Widget>[
                          TextButton(
                            onPressed: () {
                              Navigator.pop(context);
                            },
                            child: const Text('OK'),
                          ),
                        ],
                      );
                    },
                  );
                },
              onSubmitted: (String value) async { // 觸發送出行為,會把文字長度印出來
                await showDialog<void>(
                  context: context,
                  builder: (BuildContext context) {
                    return AlertDialog(
                      title: const Text('Thanks!'),
                      content: Text(
                          'You typed "$value", which has length ${value.characters.length}.'),
                      actions: <Widget>[
                        TextButton(
                          onPressed: () {
                            Navigator.pop(context);
                          },
                          child: const Text('OK'),
                        ),
                      ],
                    );
                  },
                );
              },
              ),
            ],
          ),
        );
      }
    }
    
    

TextFormField

  • TextFormField是TextFieldFormField功能的整合,可以直接在Form之中使用。額外包含了一些表單相關功能如:驗證規則(validator)、儲存時的行為(onSaved)
    TextFormField(
      decoration: const InputDecoration(
        border: UnderlineInputBorder(),
        labelText: 'Enter your username',
        validator: (value) {
            if (value == null || value.trim() == '') {
              return '此欄位為必填';
            }
            return null;
        },
        onSaved: (value) {
            print('onSaved!')        
        },
      ),
    ),
    

ListView

  • 可以依照垂直或水平方向顯示一整排的組件
  • 內部包括SingleChildScrollView,所以ListView包括了捲軸滑動的行為
  • 依照constructor可以分為三種
    • builder: 建立一連串連續排列的區塊構成的列表
    • separated: 建立一連串區塊且中間有分隔線的列表
    • custom: 建立自訂義的子組件
  • 重要的properties有:
    • itemCount: 告訴ListView列表項目的數量
    • itemBuilder: 定義項目的UI和資料,交由ListView繪製
    • (使用seperated constructor時)`separatorBuilder: 定義項目之間區隔區塊的UI
    final List<String> entries = <String>['A', 'B', 'C'];
    final List<int> colorCodes = <int>[600, 500, 100];
    
    ListView.separated(
      padding: const EdgeInsets.all(8),
      itemCount: entries.length,
      itemBuilder: (BuildContext context, int index) {
        return Container(
          height: 50,
          color: Colors.amber[colorCodes[index]],
          child: Center(child: Text('Entry ${entries[index]}')),
        );
      },
      separatorBuilder: (BuildContext context, int index) => const Divider(),
    );
    
    

專案實作

  1. 在主頁面將favorite按鍵內容改為Icon.favorite構成的愛心圖示

    ElevatedButton(
        style: ElevatedButton.styleFrom(backgroundColor: Colors.white24),
        onPressed: () {
            print('add to favorite');
        },
        child: Icon(
            Icons.favorite,
            color: Colors.pink[200],
    )),
    

  2. 在主頁面最下方加入TextInput,並加入Decorator讓它變成白色輸入框,外層包上Padding

    const Padding(
      padding: EdgeInsets.all(10.0),
      child: TextField(
        decoration: InputDecoration(
          filled: true,
          fillColor: Colors.white,
          contentPadding: EdgeInsets.all(10),
          border: InputBorder.none,
        ),
        keyboardType: TextInputType.number,
        maxLines: 8,
        minLines: 3,
      ),
    ),
    
  3. 在TextInput加入controller,以便取得TextInput內容

    TextField(
        controller: _controller,
        decoration: const InputDecoration(
          filled: true,
          fillColor: Colors.white,
          contentPadding: EdgeInsets.all(10),
          border: InputBorder.none,
        ),
        maxLines: 5,
        minLines: 3,
    ),
    
  4. 在TextInput右方加入「儲存」按鈕,按下時會將TextInput內的內容改以Text顯示,按鈕變成「編輯」

    // 加在MyHomeState class之中
    final TextEditingController _controller = TextEditingController();
    NoteType _noteType = NoteType.editable;
    
    // 加在build method的圖片說明文字Text的下面
    _noteType == NoteType.text
        ? Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Expanded(
                  child: Container(
                      padding: const EdgeInsets.all(10.0),
                      child: Text(_controller.text))),
              Container(
                  width: 100,
                  padding: const EdgeInsets.all(10),
                  child: OutlinedButton(
                      style: OutlinedButton.styleFrom(
                        fixedSize: const Size(50, 50),
                      ),
                      onPressed: () {
                        setState(() {
                          _noteType = NoteType.editable;
                        });
                      },
                      child: const Icon(Icons.edit)))
            ],
          )
        : Row(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(10.0),
                  child: TextField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      filled: true,
                      fillColor: Colors.white,
                      contentPadding: EdgeInsets.all(10),
                      border: InputBorder.none,
                    ),
                    maxLines: 5,
                    minLines: 3,
                  ),
                ),
              ),
              Container(
                width: 100,
                padding: const EdgeInsets.all(10),
                child: OutlinedButton(
                  style: OutlinedButton.styleFrom(
                    fixedSize: const Size(50, 50),
                  ),
                  onPressed: () {
                    setState(() {
                      _noteType = NoteType.text;
                    });
                  },
                  child: const Icon(
                    Icons.save,
                  ),
                ),
              )
            ],
          )
          ],
       ),
    ),
    
    
    • 編輯模式
    • 文字模式
  5. FavoritePage 加入ListView。製作列表項目:以Card顯示,並在Card上加入需要的標題。

    ListView.builder(
      itemBuilder: (BuildContext context, int index) {
        return Container(
          height: 100,
          padding: const EdgeInsets.all(5.0),
          child: const Expanded(
            child: Card(
                elevation: 5.0,
                child: Center(
                  child: Text(
                    'I am Image Title',
                    style: TextStyle(
                        fontSize: 20, fontWeight: FontWeight.w500),
                  ),
                )),
          ),
        );
      },
      itemCount: 2,
    ),
    

  • 本次改動的相關程式碼放在我的github,見Day15相關commit

Recap

今天介紹了三的Widget: Icon, TextInput, ListView
都是很常使用的組件喔!

明天一起來看看Custom Widget,如何將一些組件抽出來形成一個自定義的組件,以應付後面各種愈來愈複雜的需求。


上一篇
Flutter介紹:取得外部資料 - networking
下一篇
Flutter介紹:頁面的建構 - Custom Widget
系列文
Flutter 30: from start to store30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言